/* Copyright (C) 2021 Rain */

/* This file is part of XNIX. */

/* 
  XNIX is free software: you can redistribute it and/or modify 
  it under the terms of the GNU General Public License as published by 
  the Free Software Foundation, either version 3 of the License, or 
  (at your option) and later version. 
*/

/*
  XNIX is distributed in the hope that it will be useful, 
  but WITHOUT ANY WARRANTY; without even the implied warranty of 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
  GNU General Public License for more details. 
*/

/*
  You should have received a copy of the GNU General Public License 
   along with XNIX. If not, see <https://www.gnu.org/licenses/>.  
*/



#include <kernel/init.h>
#include <kernel/types.h>
#include <kernel/errno.h>

#include <kernel/console.h>
#include <kernel/vsprintf.h>
#include <kernel/tty.h>

#include <lib/string.h>
#include <arch/io.h>

#include <stdarg.h>


static __uint16_t *fb = FB_ADDR;
__uint8_t x, y;

struct tty tty0;

extern struct fifo kbd_fifo;
int nc_putc(struct tty *, char);


errno_t console_init(void) {
	/* stdout will be the tty1 if com0 exists */
	stdout = &tty0;

	x = bootarg->x, y = bootarg->y;

	/* setup the frame buffer address fb */
	/* note that `fb` is a 16-bits pointer, so we don't need to '<< 1' */
	fb += y * VGA_X + x;

	tty0.input_fifo = NULL;
	tty0.mode = T_COOKED | T_NORD;

	tty0.ctl = 0;
	
	/* this is virtual */
	tty0.vx = x;
	tty0.vy = y;

	tty0.putc = nc_putc;

	tty0.next = NULL;

	/* well, this is the end */

	return 0;
}

int nc_putc(struct tty *tty, char c) {
	if (c == '\n') {
		tty->vy++;
		tty->vx = 0;

		/* no roll screen for tty->vy */

		putc('\n', white);

		return 0;
	}

	else if (c == '\b') {
		if (tty->vx) 
			putc('\b', white);

		else 
			return 0;
	}

	else if (c == '\t') {
		__uint64_t ax = (tty->vx + TAB_SIZE) & ~TAB_SIZE;

		if (ax == tty->vx) 
			tty->vx += TAB_SIZE;

		else 
			tty->vx = ax;

		putc('\t', white);

		return 0;
	}

	else if (c == '\r') {
		tty->vx = 0;

		putc('\r', white);

		return 0;
	}

	putc(c, white);
	tty->vx++;

	return 0;
}


void putc(char c, __uint8_t color) {
	if (c == '\n') {
		/* we do it like \r\n */
		x = 0;

		if (++y == VGA_Y) {
			roll_screen();
			y = 24;
		}

		update_cursor();

		return;
	}

	else if (c == '\r') {
		x = 0;
		update_cursor();

		return;
	}

	if (c == '\b') {
		if (x) {
			x--;
			update_cursor();
			*fb = 0x0720;
		}

		return;
	}

	else if (c == '\t') {
		__uint64_t ax = (x + TAB_SIZE - 1) & ~(TAB_SIZE - 1);

		if (ax == x) 
			x += TAB_SIZE;

		else 
			x = ax;

		if (x == VGA_X) {
			y++;
			x = TAB_SIZE;
		}

		update_cursor();
		
		return;
	}

	__asm__ __volatile__ (
		/* rdi is the frame buffer, and rax is the  */
		"cld\n\t"
		"movb %%cl, %%ah\n\t"
		"stosw\n\t"
		
		: "+D" (fb)
		: "a" (c), "c" (color)
	);

	if (++x == VGA_X) {
		x = 0;

		if (++y == VGA_Y) {
			roll_screen();
			y = 24;
		}
	}

	update_cursor();

	return;
}


__uint16_t read_cursor(void) {
	__uint16_t pos = 0;

	outb(CMD_POS_LO, CURSOR_INDEX);
	pos |= inb(CURSOR_DATA);

	outb(CMD_POS_HI, CURSOR_INDEX);
	pos |= ((__uint16_t) inb(CURSOR_DATA)) << 8;

	return pos;
}

void update_cursor(void) {
	if (y > VGA_Y) {
		roll_screen();
		y = 24;
	}

	__uint16_t pos = y * bootarg->vga_x + x;
	fb = FB_ADDR + pos;

	outb(CMD_POS_LO, CURSOR_INDEX);
	outb(pos & 0xff, CURSOR_DATA);

	outb(CMD_POS_HI, CURSOR_INDEX);
	outb((pos >> 8) & 0xff, CURSOR_DATA);

	return;
}


void roll_screen(void) {
	/* roll down the screen */

	__asm__ __volatile__ (
		"cld\n\t"
		"movq $960, %%rcx\n\t"
		"movq $0xb80a0, %%rsi\n\t"
		"movq $0xb8000, %%rdi\n\t"
		"rep\n\t"
		"movsq\n\t"

		"movq $0xb8f00, %%rdi\n\t"
		"movq $0x0720, %%rax\n\t"
		"movq $80, %%rcx\n\t"
		"rep\n\t"
		"stosw\n\t"
		:::
	);

	return;
}
